Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

 <<   zurück
Visual Basic 2005 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual Basic 2005

Visual Basic 2005
1.233 S., mit 2 CDs, 59,90 Euro
Galileo Computing
ISBN 3-89842-585-1
gp Kapitel 7 Weitere Möglichkeiten von Visual Basic 2005
  gp 7.1 Operatorüberladung
    gp 7.1.1 Die Syntax der Operatorüberladung
    gp 7.1.2 Beispiel einer Operatorüberladung
    gp 7.1.3 Überladung der Operatoren »True« und »False«
    gp 7.1.4 Benutzerdefinierte Konvertierungen
  gp 7.2 Collections (Auflistungen)
    gp 7.2.1 Die elementaren Schnittstellen der Auflistungsklassen
    gp 7.2.2 Die Schnittstelle »IList«
    gp 7.2.3 Die Klasse »ArrayList«
    gp 7.2.4 Das Sortieren der Elemente einer »ArrayList«
    gp 7.2.5 Die Schnittstelle »IDictionary«
    gp 7.2.6 Die Klasse »Hashtable«
    gp 7.2.7 Die Klassen »Queue« und »Stack«
    gp 7.2.8 Objektauflistungen im Überblick
    gp 7.2.9 Benutzerdefinierte Auflistungen
  gp 7.3 Generics
    gp 7.3.1 Ein paar allgemeine Worte
    gp 7.3.2 Die Typproblematik am Beispiel der Klasse »Stack«
    gp 7.3.3 Die Lösung mit einer generischen Klasse
    gp 7.3.4 Typparameter mit Constraints einschränken
    gp 7.3.5 Generische Methoden
    gp 7.3.6 Generics und Vererbung
    gp 7.3.7 Generische Klassen in der .NET-Klassenbibliothek
  gp 7.4 Fortgeschrittene Delegat-Techniken
    gp 7.4.1 Eine Beispielanwendung
    gp 7.4.2 Multicast-Delegaten
  gp 7.5 Attribute
    gp 7.5.1 Das »Flags«-Attribut
    gp 7.5.2 Anmerkungen zu den Attributen
    gp 7.5.3 Benutzerdefinierte Attribute


Galileo Computing

7.4 Fortgeschrittene Delegat-Techniken  downtop


Galileo Computing

7.4.1 Eine Beispielanwendung  downtop

Stellen Sie sich vor, Sie hätten den Auftrag bekommen, eine Software zu entwickeln, die eine Pumpenanlage zum Befüllen des Schwimmbeckens eines Schwimmbades ansteuert. Es handelt sich bei dieser Anlage um Pumpen verschiedener Hersteller. Grundsätzlich sollen alle Pumpen eingeschaltet werden, wenn das Becken gefüllt wird. Anzahl und Typ der Pumpen können dabei durchaus variieren. Ihre Software soll so flexibel sein, sich an solche Änderungen automatisch anpassen zu können. Wie kann das Problem am besten gelöst werden?

Man kann davon ausgehen, dass jede Pumpe anders angesteuert werden muss. Daher bietet es sich an, für jede in Frage kommende Pumpe eine eigene Klasse mit einer Methode zu entwickeln, aus der heraus die Pumpe gestartet wird. Wir wollen zunächst zwei Klassen bereitstellen, PumpeA und PumpeB, deren Methoden SwitchOnA und SwitchOnB für die komplexen Einschaltvorgänge stehen sollen.


Class PumpeA
Public Sub SwitchOnA()
Console.WriteLine("Pumpe A wird eingeschaltet")
End Sub
End Class
Class PumpeB
Public Sub SwitchOnB()
Console.WriteLine("Pumpe B wird eingeschaltet")
End Sub
End Class

Eine Pumpenanlage, in der je eine Pumpe von jedem Typ installiert ist, soll nun eingeschaltet werden. Dazu implementieren wir eine Klasse, welche die Ansteuerung des Startvorgangs der Pumpen übernimmt. Eine weitere Klasse ist als Komponente in der Benutzeranwendung implementiert und ruft eine Methode in der pumpensteuernden Klasse auf, mit welcher der Startvorgang in Gang gesetzt wird.

Wenden wir uns zunächst der Klasse zu, die eine Methode bereitstellen soll, aus der heraus die pumpenspezifischen Startmethoden aufgerufen werden. Im einfachsten Fall könnte der Code wie folgt lauten:


Class ControlPumps
Public Sub StartAllPumps()
Dim p1 As New PumpeA
Dim p2 As New PumpeB
p1.SwitchOnA()
p2.SwitchOnB()
End Sub
End Class

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 7.3     Starten der Pumpen über eine steuernde Klasse

Ein Client könnte nun mit


Dim obj As New ControlPumps
obj.StartAllPumps()

zwar das Füllen des Beckens in die Wege leiten, aber dieser Ansatz hat einen ganz wesentlichen Nachteil: Ihm fehlt die Flexibilität, der Steuerung eine oder auch mehrere Pumpen dynamisch hinzufügen zu können und deren spezifische Startmethode aufzurufen. Die Klasse ControlPumps, die den Kern der gesamten Anwendung darstellt, müsste mit jeder neu installierten Pumpe ausgetauscht werden. Das gilt selbstverständlich auch, wenn eine Pumpe deinstalliert wird. Was ist außerdem, wenn ein Pumpenhersteller einen neuen Typ auf den Markt bringt, der für den Schwimmbadbetreiber vielleicht aufgrund der Leistungsdaten interessant ist? Die Ergänzung oder der Austausch der installierten Pumpen wäre nicht ohne Komplikationen – und das nur, weil unsere Steuerungssoftware kläglich versagt.

Wir haben es mit zwei Problemen zu tun, die gelöst werden müssen:

1. das Starten einer beliebigen Anzahl von Pumpen aus der Methode StartAllPumps heraus
       
2. die Standardisierung des Aufrufs der spezifischen Startmethoden
       

Der zweite Punkt bringt uns sofort in Erinnerung, dass ein Delegat ein Funktionsprototyp ist und einen allgemeinen Methodenaufruf ermöglicht. Auch ohne bisher einen programmiertechnischen Ansatz entwickelt zu haben, wissen wir zumindest schon, welcher Weg uns zum Ziel führt.

Es fehlt im Wesentlichen nur noch eine Idee zur Lösung des ersten Punktes. Ein möglicher Lösungsansatz könnte ein Array sein, dessen Elemente die verschiedenen Pumpen referenzieren. Dieses Array könnte elementweise durchlaufen werden. Dazu bietet sich eine Collection an, beispielsweise ArrayList, die sich insbesondere durch einfache Programmierbarkeit auszeichnet.

Sehen wir uns den Code in der Klasse ControlPumps an, der unter Einbeziehung eines ArrayList-Objekts unsere Bedingungen erfüllt:


Public Delegate Sub PumpDelegate()
Class ControlPumps
Private colPumps As New ArrayList
Public Sub AddPump(ByVal newPump As PumpDelegate)
colPumps.Add(newPump)
End Sub
Public Sub StartAllPumps()
Dim del As PumpDelegate
For Each del In colPumps
del()
Next
End Sub
End Class

Verantwortlich für das Verhalten der Klasse ist das Feld colPumps, das vom Typ der Collection ArrayList ist. Das Feld wird initialisiert, sobald ein Benutzer ControlPumps instanziiert. ColPumps dient aber nicht dazu, Pumpenobjekte zu verwalten. Diese spielen bei genauer Betrachtung eine eher untergeordnete Rolle, denn vielmehr sind die Methoden von Interesse, mit denen die Pumpen gestartet werden. Aus diesem Blickwinkel heraus drängt sich die Idee auf, vom Auflistungsobjekt Delegaten verwalten zu lassen, die einen entsprechenden Zeiger auf die zu einer Pumpe gehörende Startmethode kapseln.

Die Methode, mit der eine Pumpe – oder präzise ausgedrückt deren Startmethode – zu dem Objekt colPumps hinzugefügt wird, lautet AddPump. AddPump empfängt vom Aufrufer im Parameter newPump einen Delegaten vom Typ PumpDelegate. Dessen Referenz wird unter Aufruf der Methode Add dem ArrayList-Objekt übergebeben. Die Methode StartAllPumps ist an Einfachheit kaum noch zu übertreffen. In einer For Each-Schleife wird jedes Element der Auflistung colPumps durchlaufen und ausgeführt, was in unserem Beispiel zur Ausführung einer spezifischen Pumpenstartmethode führt.

Widmen wir uns nun dem Client und betrachten den Code, mit dem ein Delegat, der bekanntermaßen die Startmethode eines bestimmten Pumpenobjekts beschreibt, an die AddPump-Methode übergeben wird:


Dim obj As New ControlPumps
Dim p1 As New PumpeA
Dim pumpDel As PumpDelegate = _
New PumpDelegate(AddressOf p1.SwitchOnA)
obj.AddPump(pumpDel)

Wir besorgen uns zuerst sowohl eine Instanz der Klasse ControlPumps als auch die einer Pumpe. Im Codefragment handelt es sich um eine Pumpe vom Typ PumpeA. Um die Pumpe zu starten, benötigt das ControlPumps-Objekt einen Delegaten auf die Startmethode der Pumpe. Dieser wird in der dritten Codezeile erzeugt und in der vierten der AddPump-Methode als Argument übergeben.

Weil im weiteren Verlauf die Referenz auf den Delegaten nicht mehr benötigt wird, können wir den Programmcode auch etwas kürzer schreiben, indem wir den Delegaten direkt beim Aufruf der AddPump-Methode erzeugen:


Dim p1 As New PumpeA
obj.AddPump(New PumpDelegate(AddressOf p1.SwitchOnA))

Nehmen wir an, dass vier Pumpen gestartet werden sollen, würde die Definition der Benutzerklasse wie folgt lauten:


' ----------------------------------------------------------
' Beispiel: ...\Kapitel 7\SimpleDelegate
' ----------------------------------------------------------
Module Module1
Sub Main()
Dim obj As New ControlPumps
' erste Pumpe
Dim p1 As New PumpeA
obj.AddPump(New PumpDelegate(AddressOf p1.SwitchOnA))
' zweite Pumpe
Dim p2 As New PumpeB
obj.AddPump(New PumpDelegate(AddressOf p2.SwitchOnB))
' dritte Pumpe
Dim p3 As New PumpeA
obj.AddPump(New PumpDelegate(AddressOf p3.SwitchOnA))
' vierte Pumpe
Dim p4 As New PumpeA
obj.AddPump(New PumpDelegate(AddressOf p4.SwitchOnA))
' Pumpen starten
obj.StartAllPumps()
Console.ReadLine()
End Sub
End Module

Die Ausgabe im Befehlsfenster lautet:


Pumpe A wird eingeschaltet
Pumpe B wird eingeschaltet
Pumpe A wird eingeschaltet
Pumpe A wird eingeschaltet

Das Ergebnis ist perfekt, wir haben das Ziel erreicht. Die Klasse ControlPumps ist so flexibel implementiert, dass sie nicht nur die Belange eines Schwimmbads abdeckt, sondern überall dort eingesetzt werden könnte, wo Pumpen der Reihe nach eingeschaltet werden müssen. Eigentlich ist diese Aussage falsch, denn wir können sie sogar auf jedwede beliebige Komponente ausdehnen, unter der Voraussetzung, dass in der Komponente eine parameterlose Methode aufgerufen werden soll. Die Verhaltensweise, die von der Methode beschrieben wird, spielt dabei keine Rolle – alles dank der Delegaten.


Galileo Computing

7.4.2 Multicast-Delegaten  toptop

.NET bietet die Möglichkeit, mehrere Delegaten zu einem einzigen zusammenzufassen. Dadurch entsteht ein Delegaten-Verbund, der auch als Multicast-Delegate bezeichnet wird. Der Vorteil ist, dass durch den Aufruf eines Delegaten mehrere Delegaten der Reihe ausgeführt werden können. Sehen Sie sich dazu noch einmal unser Beispiel SimpleDelegate des vorhergehenden Abschnitts an. Es wird darin von vier Pumpen ausgegangen, deren Startmethoden nacheinander von je einem Delegaten eingebunden werden. In der steuernden Klasse bedarf es eines Objekt-Arrays, um alle Delegaten zu verwalten.

Der Programmcode ist wesentlich einfacher und übersichtlicher, wenn ein Multicast-Delegate die Aufgabe übernimmt. Dies soll das folgende Beispiel zeigen, das unter denselben Vorgaben wie das Beispiel SimpleDelegate entwickelt worden ist. Die Klassendefinitionen der Pumpen haben sich natürlich auch jetzt nicht verändert.


' ----------------------------------------------------------
' Beispiel: ...\Kapitel 7\MulticastDelegate
' ----------------------------------------------------------
Public Delegate Sub PumpDelegate()
Module Module1
Sub Main()
Dim obj As New ControlPumps
' Array vom Typ PumpDelegate
Dim del(3) As PumpDelegate
' Pumpenobjekte erzeugen
Dim p1 As New PumpeA
Dim p2 As New PumpeB
Dim p3 As New PumpeA
Dim p4 As New PumpeA
' die Startmethoden der Pumpen durch ein Delegate-
' Objekt beschreiben
del(0) = New PumpDelegate(AddressOf p1.SwitchOnA)
del(1) = New PumpDelegate(AddressOf p2.SwitchOnB)
del(2) = New PumpDelegate(AddressOf p1.SwitchOnA)
del(3) = New PumpDelegate(AddressOf p1.SwitchOnA)
' alle Delegaten kombinieren
Dim arrDel As PumpDelegate = PumpDelegate.Combine(del)
' das Delegate-Array an die Steuerklasse übergeben
obj.AddPump(arrDel)
' die Pumpen starten
obj.StartAllPumps()
Console.ReadLine()
End Sub
End Module
' Steuerklasse
Class ControlPumps
Private delPumps As PumpDelegate
Public Sub AddPump(ByVal pumps As PumpDelegate)
delPumps = pumps
End Sub
Public Sub StartAllPumps()
delPumps()
End Sub
End Class
...

Werfen wir zuerst einen Blick auf den Code in Main. Es fällt als Erstes auf, dass die den Pumpenobjekten zugeordneten Delegaten nun zu Elementen des Arrays del werden. Das hätten wir natürlich auch schon im Code des Beispiels SimpleDelegate so machen können. Nun steckt aber eine ganz bestimmte Absicht dahinter, die in der darauf folgenden Anweisung deutlich wird:


Dim arrDel As PumpDelegate = PumpDelegate.Combine(del)

Da Delegaten wie alles in der .NET-Welt Objekte sind, wird dieser Typ durch eine eigene Klasse im Namespace System beschrieben: Delegate. Mit der statischen Methode Combine dieses Typs lassen sich mehrere Delegaten miteinander verknüpfen. Combine ist wie folgt überladen:


Public Shared Function Combine(ParamArray del As Delegate()) _
As Delegate
Public Shared Function Delegate Combine(Delegate, Delegate) _
As Delegate

Sie können als Argument entweder ein Array vom Typ Delegate übergeben oder haben die Alternative, zwei Delegaten miteinander zu verknüpfen. Der Rückgabewert ist in beiden Fällen ein Delegat, oder präziser, es handelt sich um ein Multicast-Delegate, für den es in der Klassenbibliothek eine eigene Typdefinition gibt, die von Delegate abgeleitet ist: MulticastDelegate. Der Rückgabewert wird nach einer expliziten Konvertierung dem benutzerdefinierten Delegaten zugewiesen.

Wir haben nun eine Objektvariable namens arrDel, die ein Multicast-Delegate referenziert, der seinerseits vier Singlecast-Delegaten kombiniert. Dem Objekt der Steuerklasse müssen wir jetzt nur noch die Referenz arrDel übergeben und können uns daher in der Klassendefinition von ControlPumps das aggregierte ArrayList-Objekt ersparen. Das hat auch zur Folge, dass die Methoden AddPump und StartAllPumps an die neue Situation angepasst werden müssen. Insgesamt reduziert sich der Code und wird dadurch deutlich einfacher.

Führen Sie das Programm aus, wird an der Konsole dieselbe Ausgabe erscheinen wie im Beispiel aus Abschnitt 7.4.1:


Pumpe A wird eingeschaltet
Pumpe B wird eingeschaltet
Pumpe A wird eingeschaltet
Pumpe A wird eingeschaltet

Methoden eines Multicast-Delegaten

Jeder Delegat steht für eine Liste von Methodenaufrufen, die durchlaufen wird, sobald der Delegat ausgeführt wird. Im Falle eines Singlecast-Delegaten enthält diese Liste nur ein Element, bei einem Multicast-Delegaten können es mehrere sein. Auf diese Aufrufliste können Sie mit der Methode GetInvocationList der Klasse Delegate bzw. MulticastDelegate zugreifen, der Rückgabewert ist ein Delegate-Array.

In der Klasse Delegate ist diese Methode wie folgt definiert:


Public Overridable Function GetInvocationList As Delegate()

Um eine Methode zu der Aufrufliste hinzuzufügen oder von ihr zu entfernen, definiert die Delegate-Klasse die beiden statischen Methoden Combine und Remove. Wir hatten in unserem Beispiel oben einen Multicast-Delegaten erzeugt, indem wir vier Singlecast-Delegaten in ein Array zusammengefasst und als Argument der Combine-Methode übergeben haben. Die überladene Version dieser Methode wollen wir uns zusammen mit der Remove-Methode anschauen:


Public Shared Function Delegate Combine(Delegate, Delegate) _
As Delegate
Public Shared Function Remove(source As Delegate, _
value As Delegate) As Delegate

Beide Parameterlisten sind identisch und erwarten sowohl im ersten als auch im zweiten Argument die Referenz auf einen Delegaten. Dem ersten Parameter wird die Referenz auf den Delegaten übergeben, zu dessen Aufrufliste ein weiterer Delegat hinzugefügt bzw. im Fall der Remove-Methode entfernt werden soll. Der zweite Parameter beschreibt den hinzuzufügenden bzw. zu entfernenden Delegaten. Dazu ein kleines Beispiel:


Dim del1 As PumpDelegate = New PumpDelegate(AddressOf p1.SwitchOnA)
Dim del2 As PumpDelegate = New PumpDelegate(AddressOf p2.SwitchOnB)
del2 = Delegate.Combine(del2, del1)
...
del = Delegate.Remove(del2, del1)

In der dritten Codezeile wird der Delegat zu einem Multicast-Delegaten, in der letzten wird diese Zuordnung wieder aufgehoben. Es ist möglich, im zweiten Argument einen Multicast-Delegaten anzugeben, letztendlich verkleinert sich dadurch allerdings nicht der Programmcode. Bei einer Kombination mehrerer Delegaten ist daher die Variante mit der Übergabe eines Arrays vorzuziehen.

Interessiert der Name der von einem Delegaten gekapselten Methode, lässt sich das durch die schreibgeschützte Eigenschaft Method nebst weiteren Informationen in Erfahrung bringen. Der Aufruf dieser Eigenschaft liefert als Rückgabewert die Referenz auf ein Objekt vom Typ System.Reflection.MethodInfo, das die unterschiedlichsten Informationen zu einer Methode bereitstellt, beispielsweise über die Instanzeigenschaft Name den Namen der von einem Delegaten eingeschlossenen Methode.


Console.WriteLine(del1.Method().Name)

Die ebenfalls schreibgeschützte Eigenschaft Target liefert eine Referenz auf das Objekt, dessen Instanzmethode der aktuelle Delegat aufruft:


Public ReadOnly Property Target As Object

 <<   zurück
  
  Zum Katalog
Zum Katalog: Visual Basic 2005
Visual Basic 2005
bestellen
 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchtipps
Zum Katalog: Visual C# 2005






 Visual C# 2005


Zum Katalog: Fortgeschrittene Programmierung mit Visual C# 2005






 Fortgeschrittene
 Programmierung
 mit Visual C# 2005


Zum Katalog: Das Programmierhandbuch SQL Server 2005






 Das Programmier-
 handbuch
 SQL Server 2005


Zum Katalog: Einstieg in Visual Basic 2005






 Einstieg in
 Visual Basic 2005


Zum Katalog: Einstieg in Visual C# 2005






 Einstieg in
 Visual C# 2005


Zum Katalog: Konzepte und Lösungen für Microsoft-Netzwerke






 Konzepte und
 Lösungen für
 Microsoft-Netzwerke


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo








Copyright © Galileo Press 2007
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de